using System.Data.Common;
using System.Diagnostics;
using System.Text.Json;
using Configuration.DataBase.Data;
using Configuration.DataBase.Models;
using Microsoft.Extensions.Configuration;

namespace Configuration.DataBase
{
    public class ConfigurationDatabaseProvider : ConfigurationProvider, IDisposable
    {
        private readonly ConfigurationDatabaseOptions _options;
        
        private readonly ReaderWriterLockSlim _lockObj = new();
        
        private bool _isDisposed;
        
        public ConfigurationDatabaseProvider(ConfigurationDatabaseOptions options)
        {            
            this._options = options;
            
            var interval = TimeSpan.FromSeconds(3);
            
            if (options.ReloadInterval.HasValue)
            {
                interval = options.ReloadInterval.Value;
            }
            
            if (options.ReloadOnChange)
            {
                ThreadPool.QueueUserWorkItem(_ =>
                {
                    while (!_isDisposed)
                    {
                        Load();
                        Thread.Sleep(interval);
                    }
                });
            }
        }

        public void Dispose()
        {
            this._isDisposed = true;
        }

        public override IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath)
        {
            _lockObj.EnterReadLock();
            try
            {
                return base.GetChildKeys(earlierKeys, parentPath);
            }
            finally
            {
                _lockObj.ExitReadLock();
            }
        }

        public override bool TryGet(string key, out string? value)
        {
            _lockObj.EnterReadLock();
            try
            {
                return base.TryGet(key, out value);
            }
            finally
            {
                _lockObj.ExitReadLock();
            }
        }

        public override void Load()
        {
            base.Load();
            var clonedData = Data.Clone();
            try
            {
                _lockObj.EnterWriteLock();
                using var context = new ConfigurationDatabaseContext(_options.ConnectionString);
                context.Database.EnsureCreated();
                Data.Clear();
                if (!context.Settings.Any())
                {
	                var defaultSettings = new Dictionary<string, string?>(
		                StringComparer.OrdinalIgnoreCase)
	                {
		                ["WidgetOptions:EndpointId"] = "b3da3c4c-9c4e-4411-bc4d-609e2dcc5c67",
		                ["WidgetOptions:DisplayLabel"] = "Widgets Incorporated, LLC.",
		                ["WidgetOptions:WidgetRoute"] = "api/widgets"
	                };
	                
	                var defaultSettingEntities = defaultSettings
		                .Select(s => new Settings { Name = s.Key, Value = s.Value });
	                context.Settings.AddRange(defaultSettingEntities);
	                context.SaveChanges();
                }
                var settings = context.Settings.ToList();
                foreach (var setting in settings)
                {
                    var name = setting.Name;
                    var value = setting.Value;

                    if (string.IsNullOrEmpty(value))
                    {
                        Data[name] = value;
                        continue;
                    }
                    value = value.Trim();
                    if (value.StartsWith("[") && value.EndsWith("]") || value.StartsWith("{") && value.EndsWith("}"))
                    {
                        TryLoadAsJson(name, value);
                    }
                    else
                    {
                        Data[name] = value;
                    }
                }
            }
            catch (DbException)
            {
                // If DbException is thrown, restore to the original data.
                this.Data = clonedData;
                throw;
            }
            finally
            {
                _lockObj.ExitWriteLock();
            }
            // OnReload cannot be between EnterWriteLock and ExitWriteLock, or "A read lock may not be acquired with the write lock held in this mode" will be thrown.
            if (ConfigurationHelper.IsChanged(clonedData, Data))
            {
                OnReload();
            }
        }

        private void LoadJsonElement(string name, JsonElement jsonRoot)
        {
            if (jsonRoot.ValueKind == JsonValueKind.Array)
            {
                int index = 0;
                foreach (var item in jsonRoot.EnumerateArray())
                {
                    string path = name + ConfigurationPath.KeyDelimiter + index;
                    LoadJsonElement(path, item);
                    index++;
                }
            }
            else if (jsonRoot.ValueKind == JsonValueKind.Object)
            {
                foreach (var jsonObj in jsonRoot.EnumerateObject())
                {
                    string pathOfObj = name + ConfigurationPath.KeyDelimiter + jsonObj.Name;
                    LoadJsonElement(pathOfObj, jsonObj.Value);
                }
            }
            else
            {
                this.Data[name] = jsonRoot.GetString(); // Use GetString() for plain string value
            }
        }

        private void TryLoadAsJson(string name, string value)
        {
            var jsonOptions = new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip };
            try
            {
                using var jsonDoc = JsonDocument.Parse(value, jsonOptions);
                var jsonRoot = jsonDoc.RootElement;
                LoadJsonElement(name, jsonRoot);
            }
            catch (JsonException ex)
            {
                // If it is not valid json, parse it as plain string value
                this.Data[name] = value;
                Debug.WriteLine($"When trying to parse {value} as json object, exception was thrown. {ex}");
            }
        }
    }
}
